package tool

import (
	"bufio"
	"bytes"
	"fmt"
	"io/fs"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"chainmaker.org/chainmaker/smarteditor/generate/module"

	"github.com/pkg/errors"
)

const (
	Func            = "func"
	Package         = "package "
	PackageMain     = "package main"
	GoSuffix        = ".go"
	NewLine         = '\n'
	InitContract    = "InitContract"
	InvokeContract  = "InvokeContract"
	Quote           = "\""
	Slash           = "/"
	CallContractKey = "CallContract"
)

// ReadContractInfoListFromDirectory 函数读了
func ReadContractInfoListFromDirectory(path string) (*module.ContractList, error) {

	if !isDirectory(path) {
		return nil, errors.Errorf("path is not directory. path:%s", path)
	}

	contractDirectory, err := os.Open(path)
	if err != nil {
		return nil, errors.WithMessage(err, "open contract directory filed")
	}
	defer contractDirectory.Close()

	subFiles, err := contractDirectory.Readdirnames(-1)
	if err != nil {
		return nil, errors.Cause(err)
	}

	result := module.ContractList{}
	for _, file := range subFiles {
		subFile := filepath.Join(path, file)
		if !isDirectory(subFile) {
			continue
		}
		contract, err := readContractInfo(subFile)
		if err != nil {
			continue
		}
		result.Data = append(result.Data, contract)
	}
	return &result, nil
}

func readContractInfo(directory string) (*module.ContractInfo, error) {
	contractName := filepath.Base(directory)

	directoryFile, err := os.Open(directory)
	if err != nil {
		return nil, errors.WithMessage(err, "read contract info open contract directory filed")
	}

	subFiles, err := directoryFile.Readdirnames(-1)
	if err != nil {
		return nil, err
	}

	for _, subFileName := range subFiles {
		if !strings.HasSuffix(subFileName, GoSuffix) {
			continue
		}
		subFilePath := filepath.Join(directory, subFileName)
		fileData, err := ioutil.ReadFile(subFilePath)
		if err != nil {
			return nil, errors.Cause(err)
		}
		fileContentReader := bufio.NewReader(bytes.NewBuffer(fileData))

		hasInitMethod := false
		hasInvokeMethod := false
		methodLine := ""
		for {
			lineString, err := fileContentReader.ReadString(NewLine)
			if err != nil {
				break
			}
			if strings.HasPrefix(strings.TrimSpace(lineString), Func) && strings.Contains(lineString, InitContract) {
				hasInitMethod = true
				methodLine = lineString
			}
			if strings.HasPrefix(strings.TrimSpace(lineString), Func) && strings.Contains(lineString, InvokeContract) {
				hasInvokeMethod = true
				methodLine = lineString
			}
			if hasInitMethod && hasInvokeMethod {
				break
			}
		}
		if hasInitMethod && hasInvokeMethod {
			info := module.ContractInfo{}
			info.DirectoryName = contractName
			absPath, err := filepath.Abs(directory)
			if err != nil {
				fmt.Printf("get abs file path error, err:%v", err)
				break
			}
			info.Path = absPath
			start := strings.Index(methodLine, "*")
			end := strings.Index(methodLine, ")")
			info.ContractStructName = methodLine[start+1 : end]
			return &info, nil
		}
	}
	return nil, errors.Errorf("no contract file found. directory: %s", directory)
}

func ReplaceMainPackageName(path string, newPackageName string) error {
	if !isDirectory(path) {
		return errors.Errorf("path is not directory. path:%s", path)
	}
	directory, err := os.Open(path)
	if err != nil {
		return errors.WithMessage(err, "replace package name open directory filed")
	}

	subFiles, err := directory.Readdirnames(-1)
	if err != nil {
		return errors.WithMessage(err, "read sub file failed")
	}

	for _, subFileName := range subFiles {
		if !strings.HasSuffix(subFileName, GoSuffix) {
			continue
		}
		subFilePath := filepath.Join(path, subFileName)
		err := replace(subFilePath, newPackageName)
		if err != nil {
			return errors.WithMessagef(err, "replace package failed. file:%s", subFilePath)
		}
	}

	//將import路徑替換爲chainmaker
	prefix := Quote + "chainmaker.org/chainmaker/contract-sdk-go/v2" + Slash
	replace := Quote + "chainmaker" + Slash
	return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		} else if filepath.Ext(path) == ".go" {
			return Replace(path, prefix, replace, -1)
		}
		return nil
	})
	return nil
}

func ReplaceImportPackageName(dir, prefix, replace string) error {
	return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		} else if filepath.Ext(path) == ".go" {
			return Replace(path, prefix, replace, -1)
		}
		return nil
	})
}

func replace(goFile string, newPackageName string) error {
	return Replace(goFile, PackageMain, Package+newPackageName, 1)
}

func Replace(file string, old string, new string, count int) error {
	//read file context
	fileContent, err := ioutil.ReadFile(file)
	if err != nil {
		return errors.WithMessage(err, "read failed")
	}

	newFileContent := strings.Replace(string(fileContent), old, new, count)
	err = ioutil.WriteFile(file, []byte(newFileContent), 0644)
	if err != nil {
		return errors.WithMessage(err, "write failed")
	}
	return nil
}

func isDirectory(file string) bool {
	stat, err := os.Stat(file)
	if err != nil {
		return false
	}
	return stat.IsDir()
}
